package kz.kstu.scantest.desktop.ui.page;

import kz.kstu.scantest.app.delegate.api.FormDataDelegate;
import kz.kstu.scantest.app.delegate.api.GuideDelegate;
import kz.kstu.scantest.app.delegate.impl.FormDataDelegateImpl;
import kz.kstu.scantest.app.delegate.impl.GuideDelegateImpl;
import kz.kstu.scantest.business.client.reader.form.AbstractFormDataListener;
import kz.kstu.scantest.business.client.reader.form.FormReader;
import kz.kstu.scantest.business.client.config.Configuration;
import kz.kstu.scantest.business.common.form.FormType;
import kz.kstu.scantest.business.entity.data.FormData;
import kz.kstu.scantest.business.entity.guide.School;
import kz.kstu.scantest.business.util.DateUtil;
import kz.kstu.scantest.desktop.api.ScanResultTableModel;
import kz.kstu.scantest.desktop.ui.dialog.EditFormDataDialog;
import org.apache.log4j.Logger;

import javax.comm.NoSuchPortException;
import javax.comm.PortInUseException;
import javax.comm.UnsupportedCommOperationException;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import java.awt.event.*;
import java.io.*;
import java.text.ParseException;
import java.util.*;
import java.util.List;

/**
 * @author Filipp_Stankevich
 *         created: 08.09.11 13:45
 */
public class ScanPage {

    private final static Logger LOGGER = Logger.getLogger(ScanPage.class);

    private static final String COM_PORT_NAME = "COM1";

    private static final int NUMBER_COLUMN_WIDTH = 10;
    private static final int SURNAME_COLUMN_WIDTH = 100;
    private static final int DEFAULT_COLUMN_WIDTH = 25;
    private static final int FIFTH_SUBJECT_COLUMN_WIDTH = 80;
    private static final int ERROR_COLUMN_WIDTH = 200;

    private static final String EDIT_MENU_ITEM = "Редактировать";
    private static final String DELETE_MENU_ITEM = "Удалить";
    private static final String DATE_NOT_RECOGNIZED = "Не могу распознать дату: ";
    private static final String MESSAGE_TITLE = "Scantest";
    private static final String FORM_NOT_PROCESSE_ERROR_ENCODING = "Форма не обработана. Не верная кодировка";
    private static final String CAN_NOT_READ_PAGE = "Не могу прочитать страницу. ";
    private static final String OPERATION_NOT_SUPPORTED = "Не опрерация не поддерживается.";
    private static final String IO_ERROR = "Ошибка ввода/вывода.";
    private static final String PORT_IN_USE = "Порт используется.";
    private static final String PORT_NOT_FOUND = "Порт не найден.";
    private static final String TO_MANY_LISTENERS = "Число слушателей порта превышано.";
    private static final String CONTINUE_SCAN_QUESTION = " Продолжить сканирование?";
    private static final String END_SCAN_QUESTION = " Завершить сканирование?";
    private static final String SYNC_TRACK_ERROR_MESSAGE = "Нарушена синхродорожка.";
    private static final String UNKNOWN_ERROR_MESSAGE = "Неизвестная ошибка.";
    private static final String TIME_OUT_ERROR_MESSAGE = "Превышен интервал ожидания.";
    private static final String END_BUTTON_PRESSED_MESSAGE = "Нажата кнопка END.";
    private static final String CONTROL_POINT_ERROR_MESSAGE = "Нарушены контрольные точки.";
    private static final String PAPER_NOT_PROCESSED = " Лист не обработан.";
    private static final String DATA_WAS_PROCESSED = "Данные обработаны.";
    private static final String ERROR_WAS_FOUND = "Присутствуют ошибки.";
    private static final String SCAN_IN_ACTIVE = "Сначала завершите процесс сканирования";

    private JTable scanResultTable;
    private JPanel rootPanel;
    private JComboBox schoolCombo;
    private JRadioButton schoolRadioButton;
    private JRadioButton individualRadioButton;
    private JButton readFromFileButton;
    private JButton scanButton;
    private JList scanDateList;
    private JPanel scanPanel;
    private JPanel testPanel;
    private JButton checkAllButton;

    private JPopupMenu menu;
    private DefaultListModel scanDateListModel;
    private ScanResultTableModel scanResultTableModel;
    private JMenuItem editMenuItem;
    private JMenuItem deleteMenuItem;

    private FormDataDelegate formDataDelegate = new FormDataDelegateImpl();
    private FormReader formReader;


    public ScanPage() {
    }

    public JPanel getRootPanel() {
        return rootPanel;
    }

    public void initializeStaticComponents() {
        ButtonGroup buttonGroup = new ButtonGroup();
        buttonGroup.add(schoolRadioButton);
        buttonGroup.add(individualRadioButton);

        individualRadioButton.setSelected(true);
        schoolCombo.setEnabled(false);
        boolean isDebugMode = Configuration.getInstance().isDebugMode();
        readFromFileButton.setVisible(isDebugMode);

        scanDateListModel = new DefaultListModel();
        scanDateList.setModel(scanDateListModel);
        scanDateList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        scanResultTableModel = new ScanResultTableModel();
        scanResultTable.setModel(scanResultTableModel);
        scanResultTable.getTableHeader().setReorderingAllowed(false);

        menu = new JPopupMenu();
        editMenuItem = new JMenuItem(EDIT_MENU_ITEM);
        editMenuItem.addActionListener(new EditFormDataListener());
        menu.add(editMenuItem);
        deleteMenuItem = new JMenuItem(DELETE_MENU_ITEM);
        deleteMenuItem.addActionListener(new DeleteListener());
        menu.add(deleteMenuItem);

        initColumnsWidth();
        initListeners();
    }

    private void initColumnsWidth() {
        TableColumnModel columnModel = scanResultTable.getColumnModel();
        Enumeration<TableColumn> enumeration = columnModel.getColumns();
        while (enumeration.hasMoreElements()) {
            TableColumn column = enumeration.nextElement();
            column.setPreferredWidth(DEFAULT_COLUMN_WIDTH);
        }
        columnModel.getColumn(ScanResultTableModel.NUMBER_COLUMN_INDEX).setPreferredWidth(NUMBER_COLUMN_WIDTH);
        columnModel.getColumn(ScanResultTableModel.SURNAME_COLUMN_INDEX).setPreferredWidth(SURNAME_COLUMN_WIDTH);
        columnModel.getColumn(ScanResultTableModel.FIFTH_SUBJECT_COLUMN_INDEX).setPreferredWidth(FIFTH_SUBJECT_COLUMN_WIDTH);
        columnModel.getColumn(ScanResultTableModel.ERROR_COLUMN_INDEX).setPreferredWidth(ERROR_COLUMN_WIDTH);
    }

    private void initListeners() {

        scanButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                updateScanTableData();
                addDateToScanDateList(new Date());
                if (formReader == null || !formReader.isInitialized()) {
                    startScan();
                }
            }
        });
        scanResultTable.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                super.mouseClicked(e);
                if (e.getButton() == MouseEvent.BUTTON3) {
                    int[] selectedRowIndexes = scanResultTable.getSelectedRows();
                    editMenuItem.setEnabled(selectedRowIndexes.length == 1);
                    boolean deleteEnabled = true;
                    if (selectedRowIndexes.length > 0) {
                        for (int index : selectedRowIndexes) {
                            FormData formData = scanResultTableModel.getFormData(index);
                            if (formData.getProcessDate() != null) {
                                deleteEnabled = false;
                                break;
                            }
                        }
                    } else {
                        deleteEnabled = false;
                    }
                    deleteMenuItem.setEnabled(deleteEnabled);
                    menu.show(e.getComponent(), e.getX(), e.getY());
                }
            }
        });

        scanResultTable.getTableHeader().addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                TableColumnModel columnModel = scanResultTable.getColumnModel();
                int columnIndex = columnModel.getColumnIndexAtX(e.getX());
                scanResultTableModel.sortByColumn(columnIndex);
            }
        });

        readFromFileButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                updateScanTableData();
                addDateToScanDateList(new Date());
                JFileChooser fileChooser = new JFileChooser();
                int state = fileChooser.showOpenDialog(rootPanel);
                if (state == JFileChooser.APPROVE_OPTION) {
                    File file = fileChooser.getSelectedFile();
                    try {
                        FileInputStream fileInputStream = new FileInputStream(file);
                        byte[] data = new byte[fileInputStream.available()];
                        fileInputStream.read(data);
                        processedFormData(data);
                    } catch (FileNotFoundException e1) {
                        e1.printStackTrace();
                    } catch (UnsupportedEncodingException e1) {
                        e1.printStackTrace();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        });

        schoolRadioButton.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                schoolCombo.setEnabled(schoolRadioButton.isSelected());
                updateScanTableData();
            }
        });

        schoolCombo.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                updateScanTableData();
            }
        });

        scanDateList.addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                updateScanTableData();
            }
        });

        scanResultTable.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    editFormData();
                }
            }
        });

        checkAllButton.addActionListener(new CheckFormDataListener());
    }

    public void initData() {
        updateScanDateList();
        updateSchools();
        updateScanTableData();
    }

    public void updateSchools() {

        GuideDelegate guideDelegate = new GuideDelegateImpl();

        schoolCombo.removeAllItems();
        List<School> schoolList = guideDelegate.findSchools();
        for (School school : schoolList) {
            schoolCombo.addItem(school);
        }
    }

    public List<FormData> getSelectedFormDataList() {
        int[] selectedRowIndexes = scanResultTable.getSelectedRows();

        java.util.List<FormData> selectedFormDataList = new ArrayList<FormData>();
        for (int selectedRowIndex : selectedRowIndexes) {
            FormData selectedFormData = scanResultTableModel.getFormData(selectedRowIndex);
            selectedFormDataList.add(selectedFormData);
        }
        return selectedFormDataList;
    }

    private void updateScanDateList() {
        List<Date> scanDates = formDataDelegate.findScanDates();
        for (Date date : scanDates) {
            scanDateListModel.addElement(DateUtil.formatDate(date, DateUtil.SIMPLE_DATE_PATTERN));
        }
        addDateToScanDateList(new Date());
    }

    private void addDateToScanDateList(Date date) {
        String scanDate = DateUtil.formatDate(date, DateUtil.SIMPLE_DATE_PATTERN);
        if (!scanDateListModel.contains(scanDate)) {
            scanDateListModel.addElement(scanDate);
        }
        int index = scanDateListModel.indexOf(scanDate);
        scanDateList.setSelectedIndex(index);
    }

    private void updateScanTableData() {
        scanResultTableModel.clear();
        List<FormData> formDataList = getFormDataBySelectedParam();
        for (FormData formData : formDataList) {
            scanResultTableModel.addRow(formData);
        }
    }

    private void editFormData() {
        int selectedRowIndex = scanResultTable.getSelectedRow();
        if (selectedRowIndex != -1) {
            FormData formData = scanResultTableModel.getFormData(selectedRowIndex);
            FormData formDataToEdit = formDataDelegate.findFormDataById(formData.getId());
            EditFormDataDialog dialog = new EditFormDataDialog(JOptionPane.getFrameForComponent(rootPanel.getParent()));
            int dialogResult = dialog.editFormData(formDataToEdit);
            if (dialogResult == JOptionPane.OK_OPTION) {
                FormData newFormData = dialog.getFormData();
                FormData validatedFormData = formDataDelegate.validateAndSaveFormData(newFormData);
                scanResultTableModel.updateRow(validatedFormData);
            }
        }
    }

    private void processedFormData(byte[] data) {
        try {
            School school = null;
            if (schoolRadioButton.isSelected()) {
                school = (School) schoolCombo.getSelectedItem();
            }
            FormData formData = formDataDelegate.processFormData(data, school, FormType.DEFAULT);
            scanResultTableModel.addRow(formData);
            LOGGER.debug("Form data was processed.");
        } catch (UnsupportedEncodingException e) {
            LOGGER.error("Form data was not processed.", e);
            showMessage(FORM_NOT_PROCESSE_ERROR_ENCODING, JOptionPane.ERROR_MESSAGE);
        }
    }

    private boolean read(boolean isNewScan) {
        try {
            formReader.read(isNewScan);
            return true;
        } catch (IOException e) {
            showMessage(CAN_NOT_READ_PAGE + IO_ERROR, JOptionPane.ERROR_MESSAGE);
            LOGGER.error(e.getMessage(), e);
            formReader.shutdown();
            return false;
        }
    }

    private boolean startScan() {
        formReader = new FormReader(COM_PORT_NAME);
        formReader.addDataListener(new ScanFormDataListener());
        try {
            formReader.initialize();
            return read(true);
        } catch (PortInUseException e) {
            showMessage(CAN_NOT_READ_PAGE + PORT_IN_USE, JOptionPane.ERROR_MESSAGE);
            LOGGER.error(e.getMessage(), e);
            return false;
        } catch (NoSuchPortException e) {
            showMessage(CAN_NOT_READ_PAGE + PORT_NOT_FOUND, JOptionPane.ERROR_MESSAGE);
            LOGGER.error(e.getMessage(), e);
            return false;
        } catch (UnsupportedCommOperationException e) {
            showMessage(CAN_NOT_READ_PAGE + OPERATION_NOT_SUPPORTED, JOptionPane.ERROR_MESSAGE);
            LOGGER.error(e.getMessage(), e);
            formReader.shutdown();
            return false;
        } catch (IOException e) {
            showMessage(CAN_NOT_READ_PAGE + IO_ERROR, JOptionPane.ERROR_MESSAGE);
            LOGGER.error(e.getMessage(), e);
            formReader.shutdown();
            return false;
        } catch (TooManyListenersException e) {
            showMessage(CAN_NOT_READ_PAGE + TO_MANY_LISTENERS, JOptionPane.ERROR_MESSAGE);
            LOGGER.error(e.getMessage(), e);
            formReader.shutdown();
            return false;
        }
    }

    public List<FormData> getFormDataList() {
        return scanResultTableModel.getFormDataList();
    }

    private List<FormData> getFormDataBySelectedParam() {
        Date date = getScanDate();
        School school = getSchool();
        return formDataDelegate.findByDateAndSchool(date, school);
    }

    private Date getScanDate() {
        String scanDate = (String) scanDateList.getSelectedValue();
        try {
            return DateUtil.parseDate(scanDate, DateUtil.SIMPLE_DATE_PATTERN);
        } catch (ParseException e) {
            LOGGER.error("Can not parse date = " + scanDate);
            JOptionPane.showMessageDialog(getRootPanel(), DATE_NOT_RECOGNIZED + scanDateList.getSelectedValue());
            return null;
        }
    }

    private School getSchool() {
        if (schoolRadioButton.isSelected()) {
            return (School) schoolCombo.getSelectedItem();
        } else {
            return null;
        }
    }

    private void showMessage(String message, int messageType) {
        JOptionPane.showMessageDialog(getRootPanel(), message, MESSAGE_TITLE, messageType);
    }


    private class CheckFormDataListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            if (formReader == null || !formReader.isInitialized()) {
                java.util.List<FormData> formDataList = getFormDataBySelectedParam();
                if (formDataList != null && formDataList.size() > 0) {
                    for (FormData formData : formDataList) {
                        formDataDelegate.validateAndSaveFormData(formData);
                    }
                    java.util.List<FormData> errorFormDataList = formDataDelegate.findByDateAndSchoolWithErrors(getScanDate(), getSchool());
                    if (errorFormDataList.size() > 0) {
                        scanResultTableModel.updateFormTable(errorFormDataList);
                        showMessage(ERROR_WAS_FOUND, JOptionPane.INFORMATION_MESSAGE);
                    } else {
                        List<FormData> processedFormDataList = formDataDelegate.checkFormData(formDataList);
                        for (FormData formData : processedFormDataList) {
                            formDataDelegate.saveFormData(formData);
                        }
                        scanResultTableModel.updateFormTable(processedFormDataList);
                        showMessage(DATA_WAS_PROCESSED, JOptionPane.INFORMATION_MESSAGE);
                    }
                }
            } else {
                showMessage(SCAN_IN_ACTIVE, JOptionPane.INFORMATION_MESSAGE);
            }
        }
    }

    private class DeleteListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            int[] selectedRowIndexes = scanResultTable.getSelectedRows();
            List<FormData> formDataList = new ArrayList<FormData>();
            for (int rowIndex : selectedRowIndexes) {
                FormData formData = scanResultTableModel.getFormData(rowIndex);
                formDataList.add(formData);
            }

            for (FormData formData : formDataList) {
                formDataDelegate.removeFormData(formData.getId());
                scanResultTableModel.removeFormData(formData);
            }
        }
    }

    private class EditFormDataListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            editFormData();
        }
    }

    private class ScanFormDataListener extends AbstractFormDataListener {

        @Override
        public void handelFormData(byte[] data) {
            if (data[0] == FormReader.START_DATA_CODE) {
                LOGGER.debug("Form data handle was started... size = " + data.length);
                processedFormData(data);
                LOGGER.debug("Form data handle was finished.");
                read(false);
            } else {
                handleError(FormReader.CONTROL_POINT_ERROR_CODE);
            }
        }

        @Override
        public void handleEndButton() {
            LOGGER.debug("End button was pressed.");
            int state = JOptionPane.showConfirmDialog(getRootPanel(), END_BUTTON_PRESSED_MESSAGE + END_SCAN_QUESTION,
                    MESSAGE_TITLE, JOptionPane.YES_NO_OPTION);
            if (state == JOptionPane.YES_OPTION) {
                shutdown();
            } else {
                read(false);
            }
        }

        @Override
        public void handleError(int errorCode) {
            LOGGER.debug("Handle error. Error code = " + errorCode);
            String message = getErrorMessage(errorCode) + CONTINUE_SCAN_QUESTION;
            int state = JOptionPane.showConfirmDialog(getRootPanel(), message, MESSAGE_TITLE, JOptionPane.YES_NO_OPTION);
            if (state == JOptionPane.OK_OPTION) {
                read(true);
            } else {
                shutdown();
            }
        }

        @Override
        public void hasError() {
            try {
                formReader.requestError();
            } catch (IOException e) {
                super.reset();
                LOGGER.error("Can not write error request", e);
                handleError(FormReader.UNKNOWN_ERROR_CODE);
            }
        }

        private void shutdown() {
            super.reset();
            formReader.shutdown();
        }

        private String getErrorMessage(int errorCode) {
            if (errorCode == FormReader.TIME_OUT_ERROR_CODE) {
                LOGGER.debug("Time out error");
                return TIME_OUT_ERROR_MESSAGE;
            } else if (errorCode == FormReader.SYNC_TRACK_ERROR_CODE) {
                LOGGER.debug("Sync track error");
                return SYNC_TRACK_ERROR_MESSAGE + PAPER_NOT_PROCESSED;
            } else if (errorCode == FormReader.CONTROL_POINT_ERROR_CODE) {
                LOGGER.debug("Control point error");
                return CONTROL_POINT_ERROR_MESSAGE + PAPER_NOT_PROCESSED;
            } else {
                LOGGER.debug("Unknown error");
                return UNKNOWN_ERROR_MESSAGE;
            }
        }
    }
}
